# -*- coding: utf-8 -*-
"""
@Time : 2024/4/25 21:02
@Auth : Junjie Duan
@File : ld.py
    -i https://pypi.tuna.tsinghua.edu.cn/simple
    https://emojipedia.org/
    https://streamlit-emoji-shortcodes-streamlit-app-gwckff.streamlit.app/
"""


import json
import os
import random
import time
import cv2
import numpy as np
# import win32clipboard
from xml.dom.minidom import parseString


class Dnconsole:

    # 设置雷电路径
    def set_ld_path(self, console_path):
        """
            雷电路径
        @param console_path:
        @return:
        """
        # 请根据自己电脑配置
        # console_path = os.path.dirname(console_path)
        # print(console_path)

        self._console = console_path + "\\ldconsole.exe "  # 和dnonsole的区别是一个显示，一个隐藏
        self._ld = console_path + "\\ld.exe " #
        self._setting_path = console_path + "\\vms\\config"
        self._max_list = None  # 存储所有模拟器列表信息

    # 设置雷电序号
    def set_ldNum(self, ldNum):
        """
            设置雷电 序号
        """
        self._index = ldNum
        self.ld_temp_image = self.get_config('statusSettings.sharedPictures')
        # self.set_sharedPictures(self.ld_temp_image)

    def get_ldNum(self):
        return self._index

    # 获取模拟器列表
    def get_list(self, out_ldNums=None, check_=True):
        """
        :param out_ldNums: 排除的模拟器
        :param check:检查第一次和第下次的模拟器数量是否一致
        :return:每个模拟器列表包含：索引，标题，顶层窗口句柄，绑定窗口句柄，是否进入android，进程PID，VBox进程PID
        """

        def get_info():
            cmd = os.popen(self._console + 'list2')
            text = cmd.read()
            cmd.close()
            return text.split('\n')

        result = list()
        info = get_info()
        # 是否开启检查数量,避免雷电获取的命令信息有缺
        if check_ and self._max_list:
            for i in range(10):
                if len(info) == self._max_list:
                    break
                else:
                    time.sleep(1)
                    info = get_info()
            else:
                raise RuntimeError("模拟器获取数量不一致")
        else:
            self._max_list = len(info)

        for line in info:
            if len(line) > 1:
                dnplayer = line.split(',')
                # 去除模拟器序号
                if out_ldNums and type(out_ldNums) == list:
                    if int(dnplayer[0]) in out_ldNums:
                        continue
                result.append(DnPlayer(dnplayer))
        return result

    # 获取正在运行的模拟器列表
    def list_running(self) -> list:
        result = list()
        all__ = self.get_list()
        for dn in all__:
            if dn.is_running() is True:
                result.append(dn)
        return result

    # 检测指定序号的模拟器是否正在运行
    def is_running(self, check_=True) -> bool:
        all__ = self.get_list(check_=check_)
        for item in all__:
            if int(item.index) == self._index:
                return item.is_in_android

    # 执行shell命令
    def dnld(self, command: str, silence: bool = True):
        cmd = self._ld + '-s %d %s' % (self._index, command)
        if silence:
            os.system(cmd)
            return ''
        process = os.popen(cmd)
        result = process.read()
        process.close()
        return result

    # 执行adb命令,不建议使用,容易失控
    def adb(self, command: str, silence: bool = False) -> str:
        cmd = self._console + 'adb --index %d --command "%s"' % (self._index, command)
        if silence:
            os.system(cmd)
            return ''
        process = os.popen(cmd)
        result = process.read()
        process.close()
        return result

    # 安装apk 指定模拟器必须已经启动
    def install(self, path__: str):
        cmd = self._console + 'installapp --index %d --filename %s' % (self._index, path__)
        process = os.popen(cmd)
        result = process.read()
        process.close()
        return result

    # 卸载apk 指定模拟器必须已经启动
    def uninstall(self, package: str):
        cmd = self._console + 'uninstallapp --index %d --packagename %s' % (self._index, package)
        process = os.popen(cmd)
        result = process.read()
        process.close()
        return result

    # 启动App  指定模拟器必须已经启动
    def invokeapp(self, package: str):
        cmd = self._console + 'runapp --index %d --packagename %s' % (self._index, package)
        process = os.popen(cmd)
        result = process.read()
        process.close()
        return result

    # 停止App  指定模拟器必须已经启动
    def stopapp(self, package: str):
        cmd = self._console + 'killapp --index %d --packagename %s' % (self._index, package)
        process = os.popen(cmd)
        result = process.read()
        process.close()
        return result

    # 获取安装包列表
    def get_package_list(self) -> list:
        result = list()
        text = self.dnld('pm list packages', False)
        info = text.split('\n')
        for i in info:
            if len(i) > 1:
                result.append(i[8:])
        return result

    # 检测是否安装指定的应用
    def has_install(self, package: str, check_=True):
        if self.is_running(check_=check_) is False:
            return False
        return package in self.get_package_list()

    # 启动模拟器
    def launch(self):
        cmd = self._console + 'launch --index ' + str(self._index)
        process = os.popen(cmd)
        result = process.read()
        process.close()
        return result

    # 关闭模拟器
    def quit(self, index=None):
        if index:
            cmd = self._console + 'quit --index ' + str(index)
        else:
            cmd = self._console + 'quit --index ' + str(self._index)
        process = os.popen(cmd)
        result = process.read()
        process.close()
        return result

    # 重启模拟器，并打开指定应用
    def restart(self, packName):
        cmd = self._console + 'action --index %d --key call.reboot --value %s' % (self._index, packName)
        process = os.popen(cmd)
        result = process.read()
        process.close()
        return result

    # 设置屏幕分辨率为1080×1920 下次启动时生效
    def set_screen_size(self, width, hight, dip):
        cmd = self._console + f'modify --index %d --resolution {width},{hight},{dip}' % self._index
        process = os.popen(cmd)
        result = process.read()
        process.close()
        return result

    # 复制模拟器,被复制的模拟器不能启动
    def copy_simulator(self, name: str):
        cmd = self._console + 'copy --name %s --from %d' % (name, self._index)
        process = os.popen(cmd)
        result = process.read()
        process.close()
        return result

    # 添加模拟器
    def add_simulator(self, name: str):
        cmd = self._console + 'add --name %s' % name
        print(cmd)
        process = os.popen(cmd)
        result = process.read()
        process.close()
        return result

    # 设置自动旋转
    def auto_rate(self, auto_rate: bool = False):
        rate = 1 if auto_rate else 0
        cmd = self._console + 'modify --index %d --autorotate %d' % (self._index, rate)
        process = os.popen(cmd)
        result = process.read()
        process.close()
        return result

    # 改变设备信息 imei imsi simserial androidid mac值
    def change_device_data(self):
        # 改变设备信息
        cmd = self._console + 'modify --index %d --imei auto --imsi auto --simserial auto --androidid auto --mac auto' % self._index
        process = os.popen(cmd)
        result = process.read()
        process.close()
        return result

    # 改变CPU数量
    def change_cpu_count(self, number: int):
        # 修改cpu数量
        cmd = self._console + 'modify --index %d --cpu %d' % (self._index, number)
        process = os.popen(cmd)
        result = process.read()
        process.close()
        return result

    def get_cur_activity_xml(self):
        # 获取当前activity的xml信息
        self.dnld('uiautomator dump /sdcard/Pictures/activity.xml')
        time.sleep(1)
        f = open(self.ld_temp_image + '/activity.xml', 'r', encoding='utf-8')
        result = f.read()
        f.close()
        return result

    def get_user_info(self):
        xml = self.get_cur_activity_xml()
        usr = UserInfo(xml)
        if 'id' not in usr.info:
            return UserInfo()
        return usr

    # 获取当前activity名称
    def get_activity_name(self):
        text = self.dnld('"dumpsys activity top | grep ACTIVITY"', False)
        text = text.split(' ')
        for i, s in enumerate(text):
            if len(s) == 0:
                continue
            if s == 'ACTIVITY':
                return text[i + 1]
        return ''

    # 等待某个activity出现
    def wait_activity(self, activity: str, timeout: int) -> bool:
        for i in range(timeout):
            if self.get_activity_name() == activity:
                return True
            time.sleep(1)
        return False

    # 自动排序
    def sort(self):
        cmd = self._console + "sortWnd"
        process = os.popen(cmd)
        result = process.read()
        process.close()
        return result

    # 获取图片保存路径
    def get_config(self, key_name=None):
        index_setting_path = self._setting_path + "\\leidian%s.config" % self._index
        with open(index_setting_path, "r+", encoding="utf-8") as fp:
            text = fp.read()
            if key_name is None:
                return text
            elif key_name in text:
                text = json.loads(text)
                return text[key_name]

    # 修改默认图片保存路径
    # def set_sharedPictures(self, path):
    #     set_name = 'statusSettings.sharedPictures'
    #     new_config = ""  # 写入的文件
    #     path = path.replace('\\', "/")
    #     index_setting_path = self._setting_path + "\\leidian%s.config" % self._index
    #     with open(index_setting_path, "r+", encoding="utf-8") as fp:
    #         text = fp.read()
    #         if set_name in text:
    #             # 路径已存在，不重复写入
    #             if path in text:
    #                 return
    #             # 参数存在,但是路径不对,替换路径
    #             else:
    #                 fp.seek(0)
    #                 all_line = fp.readlines()
    #                 for line in all_line:
    #                     if set_name in line:
    #                         line = f'\t"{set_name}": "{path}",\n'
    #                     new_config += line
    #         # 参数不存在时,使用雷电3模板替换文件，并随机在写入部分参数，分辨率默认为960*540*160*240
    #         else:
    #             with open(path + "\\leidian_template.config", "r", encoding="utf-8") as fp:
    #                 new_config = fp.read()
    #                 new_config = new_config.replace("save_image_path", path)
    #                 new_config = new_config.replace("phoneIMEI_num", str(Dnconsole.myRandom("int", 12)))
    #                 new_config = new_config.replace("phoneIMSI_num", str(Dnconsole.myRandom("int", 12)))
    #                 new_config = new_config.replace("phoneSimSerial_num", str(Dnconsole.myRandom("int", 20)))
    #                 new_config = new_config.replace("phoneAndroidId_num",
    #                                                 format(int(Dnconsole.myRandom("hex", 16)), 'x'))
    #                 new_config = new_config.replace("macAddress_num", format(int(Dnconsole.myRandom("hex", 12)), 'x'))
    #
    #         # 有更改时,重新写入数据
    #         with open(index_setting_path, "w", encoding="utf-8") as fp:
    #             fp.write(new_config)

    # 随机指定类型和长度的整数数字
    @staticmethod
    def myRandom(str_type, len_):
        if str_type == "int":
            multiplier = 10
        elif str_type == "hex":
            multiplier = 16
        min_num = pow(multiplier, len_ - 1)
        max_num = min_num * multiplier - 1
        num = random.randint(min_num, max_num)
        return num


class DnPlayer(object):
    def __init__(self, info: list):
        super(DnPlayer, self).__init__()
        # 索引，标题，顶层窗口句柄，绑定窗口句柄，是否进入android，进程PID，VBox进程PID
        self.index = int(info[0])

        try:
            self.name = info[1]
            self.top_win_handler = int(info[2])
            self.bind_win_handler = int(info[3])
            self.is_in_android = True if int(info[4]) == 1 else False
            self.pid = int(info[5])
            self.vbox_pid = int(info[6])
        except:
            self.name = 0
            self.top_win_handler = 0
            self.bind_win_handler = 0
            self.is_in_android = False
            self.pid = 0
            self.vbox_pid = 0

    def is_running(self) -> bool:
        return self.is_in_android

    def __str__(self):
        index = self.index
        name = self.name
        r = str(self.is_in_android)
        twh = self.top_win_handler
        bwh = self.bind_win_handler
        pid = self.pid
        vpid = self.vbox_pid
        return "\nindex:%d name:%s top:%08X bind:%08X running:%s pid:%d vbox_pid:%d\n" % (
            index, name, twh, bwh, r, pid, vpid)

    def __repr__(self):
        index = self.index
        name = self.name
        r = str(self.is_in_android)
        twh = self.top_win_handler
        bwh = self.bind_win_handler
        pid = self.pid
        vpid = self.vbox_pid
        return "\nindex:%d name:%s top:%08X bind:%08X running:%s pid:%d vbox_pid:%d\n" % (
            index, name, twh, bwh, r, pid, vpid)


class UserInfo(object):
    def __init__(self, text: str = ""):
        super(UserInfo, self).__init__()
        self.info = dict()
        if len(text) == 0:
            return
        self.__xml = parseString(text)
        nodes = self.__xml.getElementsByTagName('node')
        res_set = [
            # 用户信息节点
        ]
        name_set = [
            'id', 'id', 'following', 'fans', 'all_like', 'rank', 'flex',
            'signature', 'location', 'video', 'name'
        ]
        number_item = ['following', 'fans', 'all_like', 'video', 'videolike']
        for n in nodes:
            name = n.getAttribute('resource-id')
            if len(name) == 0:
                continue
            if name in res_set:
                idx = res_set.index(name)
                text = n.getAttribute('text')
                if name_set[idx] not in self.info:
                    self.info[name_set[idx]] = text
                    print(name_set[idx], text)
                elif idx == 9:
                    self.info['videolike'] = text
                elif idx < 2:
                    if len(text) == 0:
                        continue
                    if self.info['id'] != text:
                        self.info['id'] = text
        for item in number_item:
            if item in self.info:
                self.info[item] = int(self.info[item].replace('w', '0000').replace('.', ''))

    def __str__(self):
        return str(self.info)

    def __repr__(self):
        return str(self.info)


class Display_ld(Dnconsole):

    def _windows_capture(self, index):
        img_path = f'screencap -p /sdcard/Pictures/{index}.png'
        self.dnld(img_path)
        try:
            self._img = self.imread(self.ld_temp_image + f"\\{index}.png")
        except RuntimeError as e:
            print(f"'{self.ld_temp_image}' , {index}.png")
            raise e
        return self._img

    # 截图
    def capture(self, x1=None, y1=None, x2=None, y2=None, file=None):
        """
        :param x1: x1 整形数:区域的左上X坐标
        :param y1: y1 整形数:区域的左上Y坐标
        :param x2: x2 整形数:区域的右下X坐标
        :param y2: y2 整形数:区域的右下Y坐标
        :param file: 保存文件路径，不填写则写入cv图像到属性self._img
        :return:
        """
        # 截取并写入
        self._img = self._windows_capture(self._index)
        self._img = self.cutOut(x1, y1, x2, y2)
        if not self._img is None:
            if file:
                cv2.imwrite(file, self._img)
            return 1
        return 0

    # 截取图像范围
    def cutOut(self, x1, y1, x2, y2):
        """

        @param x1:
        @param y1:
        @param x2:
        @param y2:
        @return:
        """
        if None in [x1, y1, x2, y2] or sum([x1, y1, x2, y2]) == 0:
            return self._img
        height, width = self._img.shape[:2]
        if y1 <= y2 <= height and x1 <= x2 <= width:
            return self._img[y1:y2, x1:x2]
        else:
            raise RuntimeError("x1,y1,x2,y2图像范围溢出")

    def getCVImg(self):
        return self._img


class KM_ld(Dnconsole):

    def __init__(self):
        self.setMouseDelay()
        self.enableRealMouse()

    def keyPressStr(self, key_str):
        """
        :param key_str: 字符串: 需要按下的字符串序列. 比如"1234","abcd","你好"等.
        :return:
        """
        cmd = self._console + 'action --index %d --key call.input --value %s' % (self._index, key_str)
        process = os.popen(cmd)
        result = process.read()
        process.close()
        return result

    # 鼠标 ==============================================================================================================
    def leftClick(self):
        if self.mouse_delay == 0:
            self.dnld('input tap %d %d' % (self.x, self.y))
        else:
            self.dnld('input touch %d %d %d' % (self.x, self.y, self.__randomMouseDelay()))

    def setMouseDelay(self, delay=None):
        """
        :param delay: 延迟，只能是1000的整数
        :return:
        """
        self.mouse_delay = 0 if delay is None else delay/1000

    def moveTo(self, x, y):
        self.x, self.y = x, y

    # 开启点击随机延迟
    def enableRealMouse(self, mousedelay=0):
        self.random_mousedelay = mousedelay

    # 随机延迟时间
    def __randomMouseDelay(self):
        return self.mouse_delay * (random.uniform((1 - self.random_mousedelay), 1 + self.random_mousedelay))

    # 滑动
    def swipe(self, x1, y1, x2, y2, delay: int = 0):
        if delay == 0:
            self.dnld('input swipe %d %d %d %d' % (x1, y1, x2, y2))
        else:
            self.dnld('input swipe %d %d %d %d %d' % (x1, y1, x2, y2, delay))


class LDManager(Display_ld):

    def __init__(self):
        super(LDManager, self).__init__()


class PicColor:
    # 随机创建图片,用于测试算法
    @staticmethod
    def _create_random_img(width, height, item=3):
        img = np.random.randint(0, 255, (width, height, item))
        img = img.astype(np.uint8)
        return img

    def ps_to_img(self, img1, delta_color):
        return ps_to_img(img1, delta_color)

    def _find_pic(self, img1, pic_name, delta_color, sim, method):
        img_path = self.path + os.path.sep + pic_name
        if not os.path.exists(img_path):
            raise RuntimeError(f"图片路径不存在{img_path}")
        img2 = self.imread(img_path)
        # 判断是RGB偏色还是HSV偏色,对应使用遮罩过滤
        # img1 = self.ps_to_img(img1, delta_color)
        # img2 = self.ps_to_img(img2, delta_color)
        # if gray:
        #     img1 = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)  # 转灰度单通道
        #     img2 = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)  # 转灰度单通道
        #     ret, img1 = cv2.threshold(img1, 0, 254, 0)  # 二值化
        #     ret, img2 = cv2.threshold(img2, 0, 254, 0)  # 二值化
        # result = cv2.matchTemplate(img1, img2, method)
        if method <= 5:
            # 只匹配指定的颜色图像，参数mask表示参与匹配的像素矩阵
            if delta_color and isinstance(delta_color, str):
                lower, upper = color_to_range(delta_color, 1.0)
                lower, upper = lower_upper21(lower, upper)
                mask = cv2.inRange(img2, tuple(lower), tuple(upper))
            elif delta_color and (isinstance(delta_color, list) or isinstance(delta_color, tuple)):
                lower, upper = delta_color
                # lower, upper = lower_upper21(lower, upper)
                img2_hsv = cv2.cvtColor(img2, cv2.COLOR_BGR2HSV)
                mask = cv2.inRange(img2_hsv, tuple(lower), tuple(upper))
            else:
                mask = None
            result = cv2.matchTemplate(img1, img2, method, mask=mask)

            min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)
            yloc, xloc = np.where(result >= sim)
            height, width = img2.shape[:2]
            return result, min_val, max_val, min_loc, max_loc, yloc, xloc, height, width
        elif method == 6:
            return self._find_pic_BRISK(img1, img2, delta_color, sim)

    def _findPic(self, x1, y1, x2, y2, pic_name, delta_color, sim, method, capture=True):
        # 读取图片
        if capture:
            ret = self.capture(x1, y1, x2, y2)
            if ret:
                img1 = self.getCVImg()
            else:
                raise RuntimeError("截圖失敗")
        else:
            img1 = self.getCVImg()
        return self._find_pic(img1, pic_name, delta_color, sim, method)

    # 单点比色
    def cmpColor(self, x, y, color, sim=1):
        """
        :param x: 坐标x
        :param y: 坐标y
        :param color: 颜色字符串,可以支持偏色,"ffffff-202020",最多支持一个
        :param sim:相似度(0.1-1.0) (0,255)
        :return:bool
        """
        ret = self.capture(0, 0, 0, 0)
        if ret:
            img = self.getCVImg()
        else:
            raise RuntimeError("截圖失敗")
        lower, upper = color_to_range(color, sim)
        if not lower is None:
            new_color = img[y, x]
            for i in [0, 1, 2]:
                if new_color[i] < lower[i] or new_color[i] > upper[i]:
                    return False
            return True
        return False

    # 范围找色
    def findColor(self, x1, y1, x2, y2, color, sim):
        ret = self.capture(x1, y1, x2, y2)
        if ret:
            img = self.getCVImg()
        else:
            raise RuntimeError("截圖失敗")
        lower, upper = color_to_range(color, sim)
        img_array = np.array(img)
        mask1 = np.all(img_array >= lower, axis=-1)
        mask2 = np.all(img_array <= upper, axis=-1)
        mask = np.logical_and(mask1, mask2)
        pos = np.argwhere(mask)
        if len(pos):
            return pos[0]

    # BRISK 特征检测器
    def _find_pic_BRISK(self, img1, img2, delta_color, sim, drag=False):
        # 判断是RGB偏色还是HSV偏色,对应使用遮罩过滤
        img1 = self.ps_to_img(img1, delta_color)
        img2 = self.ps_to_img(img2, delta_color)

        """
        # 初始化BRISK特征检测器
        brisk = cv2.BRISK_create()
        # 在a和b中检测关键点和描述符
        kp1, des1 = brisk.detectAndCompute(img1, None)
        kp2, des2 = brisk.detectAndCompute(img2, None)
        des1 = des1.astype('float32')
        des2 = des2.astype('float32')


        print(des1)
        print(des2)
        print(des1.size, des2.size)
        """
        # 段俊杰
        sift = cv2.SIFT_create()
        kp1, des1 = sift.detectAndCompute(img1, None)
        kp2, des2 = sift.detectAndCompute(img2, None)

        # 创建FLANN匹配器
        flann = cv2.FlannBasedMatcher()

        # 使用FLANN匹配器进行匹配
        matches = flann.knnMatch(des1, des2, k=2)

        # 定义比较函数
        def compare_ratio(match):
            # 返回第一个特征描述符距离与第二个特征描述符距离的比率
            return match[0].distance / match[1].distance

        # 进行排序
        matches = sorted(matches, key=compare_ratio)
        # 使用SANSAC过滤器进行匹配点过滤
        good_matches = []
        for m, n in matches:
            if m.distance < 0.7 * n.distance:
                good_matches.append(m)

        #img_matches = cv2.drawMatches(img1, kp1, img2, kp2, good_matches, None, flags=cv2.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS)
        # djj
        img_matches = cv2.drawMatchesKnn(img1, kp1, img2, kp2, [good_matches], None)
        if drag:
            imgshow(img_matches)
        if good_matches:
            return good_matches, kp1, img_matches

    def findTzPic(self, x1=0, y1=0, x2=None, y2=None, pic_name="", delta_color="", drag=None, capture=True):
        resoult = self._findPic(x1, y1, x2, y2, pic_name, delta_color, None, 6, capture)
        if resoult:
            good_matches, kp1, img = resoult
            m = good_matches[0]
            if drag:
                imgshow(img)
            # 浮点数转整数
            pts1 = np.array(kp1[m.queryIdx].pt, dtype=np.float32).astype(np.int32)
            return list(pts1)

    # 找图
    def findPic(self, x1=0, y1=0, x2=None, y2=None, pic_name="", delta_color="", sim=0.9, method=5, drag=None, capture=True, center=None):
        """
        :param center:
        :param x1:区域的左上X坐标
        :param y1:区域的左上Y坐标
        :param x2:区域的右下X坐标
        :param y2:区域的右下Y坐标
        :param pic_name:图片名，只能单个图片
        :param delta_color:偏色,可以是RGB偏色,格式"FFFFFF-202020",也可以是HSV偏色，格式((0,0,0),(180,255,255))
        :param sim:相似度，和算法相关
        :param method:仿大漠，总共有6总，范围0-5,对应cv的算法
        :param drag:是否在找到的位置画图并显示,默认不画
               方差匹配方法：匹配度越高，值越接近于0。
               归一化方差匹配方法：完全匹配结果为0。
               相关性匹配方法：完全匹配会得到很大值，不匹配会得到一个很小值或0。
               归一化的互相关匹配方法：完全匹配会得到1， 完全不匹配会得到0。
               相关系数匹配方法：完全匹配会得到一个很大值，完全不匹配会得到0，完全负相关会得到很大的负数。
                    （此处与书籍以及大部分分享的资料所认为不同，研究公式发现，只有归一化的相关系数才会有[-1,1]的值域）
               归一化的相关系数匹配方法：完全匹配会得到1，完全负相关匹配会得到-1，完全不匹配会得到0。
        :param capture:是否截图，或者使用之前的图片
        :param center:算法

        :return:
        """
        resoult = self._findPic(x1, y1, x2, y2, pic_name, delta_color, sim, method, capture)
        result, min_val, max_val, min_loc, max_loc, yloc, xloc, height, width = resoult
        # 画图
        if len(xloc):
            x, y = max_loc[0] + x1, max_loc[1] + y1
            try:
                drag = drag if self.drag is None else self.drag
            except Exception as e: pass
            if drag:
                img = cv2.rectangle(self.getCVImg(), (x, y), (x + width, y + height), (255, 0, 0), thickness=2)
                imgshow(img)
            if center:
                return x + width / 2, y + height / 2
            return x, y

    # 找图，返回多个匹配地址
    def findPics(self, x1, y1, x2, y2, pic_name, delta_color, sim, method=5, drag=None, capture=True):
        locs = []
        result, min_val, max_val, min_loc, max_loc, yloc, xloc, height, width = self._findPic(x1, y1, x2, y2, pic_name,
                                                                                              delta_color, sim,
                                                                                              method, capture)
        if len(xloc):
            if drag:
                for loc in zip(xloc, yloc):
                    img = cv2.rectangle(self.getCVImg(), (loc[0], loc[1]), (loc[0] + width, loc[1] + height),
                                        (255, 0, 0),
                                        thickness=2)
                    imgshow(img)
            locs = list(zip(xloc, yloc))
        return locs
        # return -1, [-1, -1]

    @staticmethod
    def imread(path):
        # 读取图片
        if PicColor.is_chinese(path):
            img = cv2.imdecode(np.fromfile(path, dtype=np.uint8), 1)  # 避免路径有中文
        else:
            img = cv2.imread(path)
        return img

    # 显示图像
    @staticmethod
    def imshow(img=None):
        """
        :param img:  cv格式图像
        :return: None
        """
        if img is None:
            raise RuntimeError("图像为空")
        cv2.imshow("img", img)
        cv2.waitKey(0)

    # 判断字符串是否为中文
    @staticmethod
    def is_chinese(string):
        """
        检查整个字符串是否包含中文
        :param string: 需要检查的字符串
        :return: bool
        """
        for ch in string:
            if u'\u4e00' <= ch <= u'\u9fff':
                return True
        return False

    def resize(self, fx=1, fy=1):
        """
        :param fx: x轴缩放倍数
        :param fy: y轴缩放倍数
        :return: img
        """
        return cv2.resize(self.getCVImg(), None, fx=fx, fy=fy)


def test_run_time(func):
    def inner(*args, **kwargs):
        t_start = time.time()
        res = func(*args, **kwargs)
        t_end = time.time()
        print(f"一共花费了{t_end - t_start}秒时间,函数运行结果是 {res}")
        return res

    return inner


def raise_dm_error(name, description):
    error = f"报错类型:{name},报错描述:{description}"
    raise error


def ps_to_img(img, ps):
    """
    :param img: cv图像
    :param ps: 偏色
    :return: 偏色后的cv图像
    """
    # 判断是RGB偏色还是HSV偏色,对应使用遮罩过滤
    if not ps:
        return img

    elif type(ps) == str:
        lower, upper = color_to_range(ps, 1)
        img = inRange(img, lower, upper)

    elif type(ps) == tuple or type(ps) == list:
        lower, upper = ps
        img_hsv1 = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
        img = inRange(img_hsv1, lower, upper)
        img = cv2.cvtColor(img, cv2.COLOR_HSV2BGR)
    return img


# 转换大漠格式RGB "ffffff-303030" 为 BGR遮罩范围(100,100,100),(255,255,255)
def color_to_range(color, sim):
    if sim <= 1:
        if len(color) == 6:
            c = color
            weight = "000000"
        elif "-" in color:
            c, weight = color.split("-")
        else:
            raise RuntimeError("参数错误")
    else:
        raise RuntimeError("参数错误")
    color = int(c[4:], 16), int(c[2:4], 16), int(c[:2], 16)
    weight = int(weight[4:], 16), int(weight[2:4], 16), int(weight[:2], 16)
    sim = int((1 - sim) * 255)
    lower = tuple(map(lambda c, w: max(0, c - w - sim), color, weight))
    upper = tuple(map(lambda c, w: min(c + w + sim, 255), color, weight))
    return lower, upper


def inRange(img, lower, upper):
    mask = cv2.inRange(img, np.array(lower), np.array(upper))
    img = cv2.bitwise_and(img, img, mask=mask)
    return img


def send_msg_to_clip(type_data, msg):
    """
    操作剪贴板分四步：
    1. 打开剪贴板：OpenClipboard()
    2. 清空剪贴板，新的数据才好写进去：EmptyClipboard()
    3. 往剪贴板写入数据：SetClipboardData()
    4. 关闭剪贴板：CloseClipboard()

    :param type_data: 数据的格式，
    unicode字符通常是传 win32con.CF_UNICODETEXT
    :param msg: 要写入剪贴板的数据
    """
    # win32clipboard.OpenClipboard()
    # win32clipboard.EmptyClipboard()
    # win32clipboard.SetClipboardData(type_data, msg)
    # win32clipboard.CloseClipboard()


# 截取图像范围
def cutOut(img, x1, y1, x2, y2):
    if None in [x1, y1, x2, y2] or sum([x1, y1, x2, y2]) == 0:
        return img
    height, width = img.shape[:2]
    if y1 <= y2 <= height and x1 <= x2 <= width:
        return img[y1:y2, x1:x2]
    else:
        raise RuntimeError("x1,y1,x2,y2图像范围溢出")


def imgshow(img):
    windows_name = "img"
    cv2.imshow(windows_name, img)
    cv2.waitKey()
    # cv2.destroyWindow(windows_name)


def lower_upper21(lower, upper):
    """
    颜色的上限和下限，-1或者+1，避免相等
    :param lower:
    :param upper:
    :return:
    """
    new_lower, new_upper = [], []
    for down, up in zip(lower, upper):
        if down == up > 0:
            down -= 1
        elif down == up == 0:
            up += 1
        new_lower.append(down)
        new_upper.append(up)
    return new_lower, new_upper


class AuToLD(Display_ld, KM_ld, PicColor):
    """
        Display_ld: 截图相关
        KM_ld：鼠标键盘相关
        PicColor: 图像识别
    """

    def __init__(self, ld_path=None, index=None, imgs_path=None):
        """

        :param ld_path:  雷电模拟器文件路径
        :param index:  模拟器序号
        :param imgs_path: 识别图片路径
        """
        super(AuToLD, self).__init__()

        self.ld_path = ld_path
        self.set_ld_path(self.ld_path)
        if imgs_path:
            self.path = imgs_path
        else:
            self.path = os.getcwd()

        self.set_ldNum(index)